This notebook will demonstrate the process of training a binary perceptron classifier on a toy two-dimensional dataset.

First we must load the dataset.

In [1]:
class1<-read.table("Class1.txt",header=TRUE,sep=",") 
class1.dim<-dim(class1)
class1.label<-rep(1,class1.dim[1])
class1<-cbind(class1,class1.label)
names(class1)<-c("weight","height","label")
class1
weightheightlabel
0.1320.7571
0.7220.8881
0.0950.8041
0.6330.5301
0.4720.7011
0.2940.1831
0.1790.8741
0.0230.6641
0.1160.9781
0.0540.7861
0.3940.9701
0.1650.3721
0.6540.9061
0.4860.8711
0.2370.6911
0.5840.9901
0.1330.3961
0.0680.2341
0.1140.4641
0.1080.2411
0.3920.6971
0.0490.3611
0.1690.8771
0.7090.8631
0.0460.6461
0.0690.5251
0.4320.8411
0.3660.8881
0.3860.5701
0.5760.9121
.........
0.5770.9021
0.0330.4731
0.7730.9381
0.3030.6801
0.5760.7211
0.3260.9891
0.4070.7191
0.4060.9211
0.6490.9051
0.5790.8411
0.0950.6211
0.2540.6461
0.0930.5701
0.1260.4201
0.3870.7101
0.0630.9961
0.0160.8671
0.1610.8851
0.0100.7001
0.1540.5641
0.5650.9611
0.3400.4821
0.4430.7631
0.7410.9481
0.3390.7611
0.8700.1321
0.7040.5641
0.8420.0521
0.9030.0101
0.6210.6911
In [2]:
class2<-read.table("Class2.txt",header=TRUE,sep=",")
class2.dim<-dim(class2)
class2.label<-rep(-1,class2.dim[1])
class2<-cbind(class2,class2.label)
names(class2)<-c("weight","height","label")
class2
weightheightlabel
0.4070.347-1
0.7260.761-1
0.6440.415-1
0.0760.143-1
0.1100.010-1
0.9070.859-1
0.8360.211-1
0.9540.706-1
0.8070.466-1
0.6190.489-1
0.6950.227-1
0.6290.153-1
0.7410.613-1
0.3110.289-1
0.4650.426-1
0.2240.102-1
0.3870.091-1
0.8960.839-1
0.2320.243-1
0.7040.321-1
0.3830.234-1
0.1710.001-1
0.9130.295-1
0.9220.166-1
0.4300.404-1
0.8190.670-1
0.3180.628-1
0.8280.412-1
0.6610.556-1
0.5090.572-1
.........
0.1290.123-1
0.8760.342-1
0.4810.567-1
0.9090.804-1
0.7210.837-1
0.6620.772-1
0.7960.566-1
0.9000.032-1
0.7020.386-1
0.8870.003-1
0.6340.377-1
0.6860.329-1
0.5320.630-1
0.4950.394-1
0.6330.706-1
0.2170.330-1
0.9990.726-1
0.9450.274-1
0.9350.321-1
0.2130.012-1
0.1650.014-1
0.7810.607-1
0.9020.957-1
0.8680.201-1
0.1980.006-1
0.0520.385-1
0.4330.986-1
0.3570.554-1
0.3030.889-1
0.0610.814-1
In [3]:
class1.2<-rbind(class1,class2)
class1.2
weightheightlabel
0.1320.7571
0.7220.8881
0.0950.8041
0.6330.5301
0.4720.7011
0.2940.1831
0.1790.8741
0.0230.6641
0.1160.9781
0.0540.7861
0.3940.9701
0.1650.3721
0.6540.9061
0.4860.8711
0.2370.6911
0.5840.9901
0.1330.3961
0.0680.2341
0.1140.4641
0.1080.2411
0.3920.6971
0.0490.3611
0.1690.8771
0.7090.8631
0.0460.6461
0.0690.5251
0.4320.8411
0.3660.8881
0.3860.5701
0.5760.9121
.........
0.1290.123-1
0.8760.342-1
0.4810.567-1
0.9090.804-1
0.7210.837-1
0.6620.772-1
0.7960.566-1
0.9000.032-1
0.7020.386-1
0.8870.003-1
0.6340.377-1
0.6860.329-1
0.5320.630-1
0.4950.394-1
0.6330.706-1
0.2170.330-1
0.9990.726-1
0.9450.274-1
0.9350.321-1
0.2130.012-1
0.1650.014-1
0.7810.607-1
0.9020.957-1
0.8680.201-1
0.1980.006-1
0.0520.385-1
0.4330.986-1
0.3570.554-1
0.3030.889-1
0.0610.814-1

We then place the data into a dataframe to make it easier to visualize:

In [4]:
d.set<-data.frame(cbind(rep(1,nrow(class1.2)),class1.2))
names(d.set)<-c("bias","weight","height","label")
d.set
biasweightheightlabel
1 0.1320.7571
1 0.7220.8881
1 0.0950.8041
1 0.6330.5301
1 0.4720.7011
1 0.2940.1831
1 0.1790.8741
1 0.0230.6641
1 0.1160.9781
1 0.0540.7861
1 0.3940.9701
1 0.1650.3721
1 0.6540.9061
1 0.4860.8711
1 0.2370.6911
1 0.5840.9901
1 0.1330.3961
1 0.0680.2341
1 0.1140.4641
1 0.1080.2411
1 0.3920.6971
1 0.0490.3611
1 0.1690.8771
1 0.7090.8631
1 0.0460.6461
1 0.0690.5251
1 0.4320.8411
1 0.3660.8881
1 0.3860.5701
1 0.5760.9121
............
1 0.1290.123-1
1 0.8760.342-1
1 0.4810.567-1
1 0.9090.804-1
1 0.7210.837-1
1 0.6620.772-1
1 0.7960.566-1
1 0.9000.032-1
1 0.7020.386-1
1 0.8870.003-1
1 0.6340.377-1
1 0.6860.329-1
1 0.5320.630-1
1 0.4950.394-1
1 0.6330.706-1
1 0.2170.330-1
1 0.9990.726-1
1 0.9450.274-1
1 0.9350.321-1
1 0.2130.012-1
1 0.1650.014-1
1 0.7810.607-1
1 0.9020.957-1
1 0.8680.201-1
1 0.1980.006-1
1 0.0520.385-1
1 0.4330.986-1
1 0.3570.554-1
1 0.3030.889-1
1 0.0610.814-1
In [5]:
plot(d.set[1:nrow(class1),]$weight,d.set[1:nrow(class1),]$height,xlim=c(0:1),ylim=c(0:1),col="red")
points(d.set[nrow(class1)+1:nrow(class1.2),]$weight,d.set[nrow(class1)+1:nrow(class1.2),]$height,col="blue")

Next we will train the perceptron. First we randomize the training data:

In [7]:
samples<-sample(nrow(d.set))
randomized.set<-d.set[samples,]
randomized.set
biasweightheightlabel
1931 0.9020.957-1
1401 0.5980.446-1
391 0.8070.960 1
201 0.1080.241 1
1741 0.9090.804-1
1721 0.8760.342-1
1321 0.7100.347-1
381 0.1980.955 1
1281 0.8280.412-1
1121 0.6290.153-1
1081 0.9540.706-1
841 0.1260.420 1
961 0.8700.132 1
411 0.5010.901 1
1201 0.7040.321-1
1261 0.8190.670-1
1301 0.5090.572-1
741 0.3030.680 1
451 0.2290.984 1
281 0.3660.888 1
651 0.0560.615 1
811 0.0950.621 1
941 0.7410.948 1
791 0.6490.905 1
831 0.0930.570 1
2001 0.0610.814-1
1631 0.9300.890-1
1421 0.4290.414-1
401 0.0810.758 1
91 0.1160.978 1
...............
191 0.1140.464 1
1171 0.3870.091-1
1791 0.7020.386-1
1701 0.6640.141-1
991 0.9030.010 1
951 0.3390.761 1
161 0.5840.990 1
1541 0.2610.097-1
291 0.3860.570 1
1771 0.7960.566-1
551 0.6380.911 1
261 0.0690.525 1
61 0.2940.183 1
1561 0.6270.600-1
431 0.1260.326 1
701 0.2700.946 1
1831 0.5320.630-1
1481 0.9040.941-1
1001 0.6210.691 1
1211 0.3830.234-1
1581 0.5180.440-1
981 0.8420.052 1
611 0.0970.537 1
331 0.0840.316 1
581 0.3340.576 1
1511 0.8170.183-1
1031 0.6440.415-1
1111 0.6950.227-1
41 0.6330.530 1
931 0.4430.763 1

Then we provide the perceptron learning algorithm:

In [8]:
perceptron <- function(x, eta, niter) {
        
        # initialize weight vector
        weight <- rep(0.1, dim(x)[2]-1)
        errors <- rep(0, niter)
        label.index<-length(x[1,])
        features<-x[,-label.index]
        labels<-x[,label.index]
        
        # loop over number of epochs niter
        for (jj in 1:niter) {
                
                # loop through training data set
                for (ii in 1:nrow(x)) 
                {
                        
                        # Predict binary label using activation function
                        z <- sum(weight[1:length(weight)] * as.numeric(features[ii,])) 
                        if(z < 0) 
                        {
                                ypred <- -1
                        } else {
                                ypred <- 1
                        }
                        
                        # Change weight - the formula doesn't do anything 
                        # if the predicted value is correct
                        weightdiff <- eta * (as.numeric(labels[ii]) - ypred) * as.numeric(features[ii,])
                        weight <- weight + weightdiff
                        
                        # update error rate
                        if ((as.numeric(labels[ii]) - ypred) != 0.0) 
                        {
                                errors[jj] <- errors[jj] + 1
                        }
                        
                }
        }
        
        # weight to decide between the two species 
        print(weight)
        print(errors)
        return(list(v1=weight,v2=errors))
}

Then we execute it using 500 iterations and eta=0.05:

In [9]:
iterations<-500
weight.err <- perceptron(randomized.set, 0.05, iterations)
[1]  0.1000 -0.2757  0.3720
  [1] 55 45 42 41 45 44 44 50 43 46 47 43 47 40 48 43 45 43 45 43 45 43 47 48 43
 [26] 45 43 45 43 45 43 45 43 47 48 43 45 43 45 40 48 43 47 44 43 45 44 43 47 42
 [51] 44 44 46 41 47 45 47 40 48 43 45 43 45 43 47 48 43 42 45 46 41 47 43 43 43
 [76] 47 44 43 47 48 43 45 43 45 43 45 43 45 43 47 48 43 45 43 45 40 48 43 47 44
[101] 43 45 44 43 47 42 44 44 46 41 47 45 47 40 48 43 45 43 45 43 47 48 43 42 46
[126] 45 44 40 43 46 45 40 43 46 45 40 43 47 44 43 47 48 43 45 43 45 43 45 43 45
[151] 43 47 48 43 45 43 45 40 48 43 47 44 43 45 44 43 47 42 44 44 46 41 47 45 47
[176] 40 48 43 45 43 45 43 47 48 43 42 45 46 41 47 43 43 42 40 40 40 40 40 40 40
[201] 40 40 40 40 40 40 45 46 47 45 43 43 47 44 43 47 48 43 45 43 45 40 48 43 47
[226] 43 47 48 43 45 44 43 47 48 43 43 46 43 45 44 43 45 43 45 43 47 44 46 41 47
[251] 43 47 42 44 44 42 45 47 47 49 43 47 40 48 43 45 43 47 48 43 42 46 45 44 40
[276] 43 46 45 40 43 46 45 40 43 47 44 43 47 48 43 45 43 45 43 45 43 45 43 47 48
[301] 43 45 43 45 40 48 43 47 47 49 43 43 44 44 43 45 43 47 48 43 45 43 45 40 48
[326] 43 47 47 49 43 43 44 44 43 45 43 47 48 43 45 43 45 43 45 43 45 43 47 48 43
[351] 45 43 45 40 48 43 47 47 49 43 43 44 44 43 45 43 47 48 43 45 43 45 43 45 43
[376] 45 43 45 43 47 44 46 41 47 45 47 40 48 43 42 45 44 44 43 42 45 44 43 45 46
[401] 43 49 40 48 43 42 46 45 44 40 42 45 47 47 49 43 47 40 48 43 45 40 48 43 47
[426] 45 47 43 47 40 48 43 45 43 45 43 45 43 47 48 43 45 43 45 43 45 43 45 43 47
[451] 48 43 45 43 45 43 45 43 45 43 47 48 43 45 43 45 43 45 43 45 43 47 48 43 45
[476] 43 45 40 48 43 47 43 47 43 47 40 48 43 47 48 43 42 45 46 43 46 45 44 43 45
In [10]:
plot(1:iterations,weight.err$v2)

Then we use the results to plot a decision surface:

In [12]:
plot(d.set[1:nrow(class1),]$weight,d.set[1:nrow(class1),]$height,xlim=c(0:1),ylim=c(0:1),col="red")
points(d.set[nrow(class1)+1:nrow(class1.2),]$weight,d.set[nrow(class1)+1:nrow(class1.2),]$height,col="blue")
slope<-weight.err$v1[2]/weight.err$v1[3]*(-1)
intercept<-weight.err$v1[1]/weight.err$v1[3]*(-1)
abline(intercept,slope,col="green",lty=2)
slope
intercept
0.741129032258194
-0.268817204301081